package
{
	import com.indiemaps.mapping.data.parsers.e00.E00Parser;
	import com.indiemaps.mapping.data.parsers.e00.E00Tools;
	import com.indiemaps.mapping.projections.LambertConformalConic;
	import com.indiemaps.visualization.flare.renderers.CircleRadiusRenderer;
	
	import flare.vis.Visualization;
	import flare.vis.controls.DragControl;
	import flare.vis.data.Data;
	import flare.vis.data.NodeSprite;
	import flare.vis.operator.layout.ForceDirectedLayout;
	
	import flash.display.Graphics;
	import flash.display.Sprite;
	import flash.events.Event;
	import flash.geom.Point;
	import flash.geom.Rectangle;
	import flash.net.URLLoader;
	import flash.net.URLLoaderDataFormat;
	import flash.net.URLRequest;
	import flash.utils.Dictionary;
	
	/**
	 * An application of the E00Parser and E00Tools classes which takes advantage of the topological nature of the data format.
	 * After loading, a graph is produced with nodes for each U.S. state, and edges connecting neighbors.
	 * The graph is produced using the Flare visualization library.
	 * Loads, projects, and draws an e00 file of the United States at run-time.
	 * Note: loading/projecting/drawing may take up to a couple dozen seconds (though should only take a few).  
	 * No progress bar or status indicators are included in the example.
	 * 
	 * 
	 * Note: this example requires the Flare AS3 visualization library, which is free and open-source but not distributed along with the E00 code. */
	[SWF(backgroundColor='#ffffff', width='1000', height='800')]
	public class E00ForceDirectedGraphExample extends Sprite
	{
		protected static const BOUNDS : Rectangle = new Rectangle( 0, 0, 1000, 500 );
		
		protected static var STANDARD_PARALLEL_1 : Number = 33.7;
		protected static var STANDARD_PARALLEL_2 : Number = 35.8;
		protected static var LATITUDE_ORIGIN : Number = 34.7;
		protected static var LONGITUDE_ORIGIN : Number = -92.4;
		
		private var e00Location : String = 'data/e00/st99_d00.e00';
		
		// our e00 file has no attribute data, so I'm loading a csv file of state populations by FIPS code
		private var popDataLocation : String = 'data/statePopulations.csv';
		
		private var mapContainer : Sprite = new Sprite();
		
		private var featureInfo : Dictionary;
		private var statePopData : Dictionary;
		
		private var zoom : Number;
		
		public function E00ForceDirectedGraphExample()
		{
			addChild( mapContainer );
			mapContainer.y = mapContainer.x = 50;
			// load e00
			var dataLoader:URLLoader = new URLLoader();
			dataLoader.dataFormat = URLLoaderDataFormat.TEXT;
			dataLoader.load( new URLRequest( e00Location ) );
			dataLoader.addEventListener( Event.COMPLETE, onE00LoadingComplete );
			
			// load state population data
			var popDataLoader : URLLoader = new URLLoader();
			popDataLoader.dataFormat = URLLoaderDataFormat.TEXT;
			popDataLoader.load( new URLRequest( popDataLocation ) );
			popDataLoader.addEventListener( Event.COMPLETE, onPopDataLoadingComplete );
			
		}
		
		private function onPopDataLoadingComplete( e : Event ) : void
		{
			var dataText : String = e.target.data as String;
			var lines : Array = dataText.split( '\n' );
			statePopData = new Dictionary();
			
			for each ( var line : String in lines ) {
				var data : Array = line.split( ',' );
				var fips : String = String( data[ 0 ] ).substr( 1, 2 );
				statePopData[ fips ] = Number( data[ 1 ] );
			}
			
			if ( featureInfo ) combineData();
		}
		
		private function onE00LoadingComplete( e : Event ) : void
		{
			var e00Text : String = e.target.data as String;
			var data : Object = E00Parser.parse( e00Text );
			
			// get a dictionary of the drawn features, projected in lambert's conformal conic	
			var featureSprites : Dictionary = E00Tools.drawAllFeatures( data, data[ 'ifo' ][ 'PAT' ][ 'data' ], 'STATE', projectionFunction, BOUNDS.width, BOUNDS.height, 
				function( graphics : Graphics ) : void { graphics.lineStyle( 0, 0xffffff, 1 ); graphics.beginFill( 0xcccccc, 1 ); }, 
				[ '02', '', 72, 15 ] );
			
			// the returned dictionary is all sprites except for 3 properties
			zoom = featureSprites[ 'zoom' ];
			var xAdjustment : Number = featureSprites[ 'xAdjustment' ];
			var yAdjustment : Number = featureSprites[ 'yAdjustment' ];
			
			// get the feature centroids
			var featureCenters : Dictionary = E00Tools.getAllFeatureCentroids( data, data[ 'ifo' ][ 'PAT' ][ 'data' ], 'STATE', projectionFunction );
			
			// add them to our map container
			for each ( var featureSprite : Object in featureSprites ) {
				if ( featureSprite is Sprite )
					mapContainer.addChild( featureSprite as Sprite );
			}
			
			// get a dictionary of each feature's neighbors and shared border length
			featureInfo = E00Tools.getAllFeatureSharedBorderLengths( data, data[ 'ifo' ][ 'PAT' ][ 'data' ], 'STATE', featureSprites[ 'zoom' ], projectionFunction );
			
			// combine the centroids and graphics into featureInfo
			for ( var key : Object in featureInfo ) {
				featureSprite = ( featureSprites[ key ] as Sprite );
				if ( ! featureSprite ) {
					delete featureInfo[ key ];
					continue;
				}
				// there's an error in the data for Texas (FIPS code 48) so we're just using the bounding box center for her
				if ( key == '48' ) {
					var featureBounds : Rectangle = featureSprite.getBounds( mapContainer );
					featureInfo[ key ][ 'centroid' ] = new Point( .5 * featureBounds.width + featureBounds.x, .5 * featureBounds.height + featureBounds.y );
				} else {
					featureInfo[ key ][ 'centroid' ] = new Point( ( featureCenters[ key ][ 'x' ] + xAdjustment ) * zoom, ( -featureCenters[ key ][ 'y' ] - yAdjustment ) * zoom );
				}
				featureInfo[ key ][ 'graphic' ] = featureSprite;
			}
			
			// combine the geodata from the e00 with the population data from the csv
			if ( statePopData ) combineData();
			
		}
		
		private function combineData() : void
		{
			for ( var key : Object in featureInfo ) {
				var population : Number = statePopData[ key ];
				if ( ! population ) {
					delete featureInfo[ key ];
					continue;
				}
				featureInfo[ key ][ 'population' ] = statePopData[ key ];
				trace( key, featureInfo[ key ][ 'population' ], featureInfo[ key ][ 'centroid' ] );
			}
			
			drawGraph();
		}
		
		private function drawGraph() : void
		{
			// for proportional symbol areas
			var maxArea : Number = 6000;
			var maxValue : Number = -Infinity;
			for each ( var feature : Object in featureInfo ) {
				var thisValue : Number = feature[ 'population' ];
				if ( thisValue > maxValue ) maxValue = thisValue;
			}
			
// --- now we're into the Flare stuff ---
			
			// the data object that will hold the graph nodes and edges
			var data : Data = new Data();
			
			// create a node for each state
			for each ( feature in featureInfo ) {
				var node : NodeSprite = new NodeSprite();
				var symbolArea : Number = feature[ 'population' ] / maxValue * maxArea;
				var symbolRadius : Number = Math.sqrt( symbolArea / Math.PI );
				node.size = symbolRadius;
				node.renderer = new CircleRadiusRenderer( 0, 0, 0, 0x0099cc, 1 );
				node.x = feature[ 'centroid' ][ 'x' ];
				node.y = feature[ 'centroid' ][ 'y' ];
				feature[ 'node' ] = node;
				
				data.addNode( node );	
			}
			
			// add an edge for each neighborhood relationship
			for each ( feature in featureInfo ) {
				for ( var neighbor : Object in feature[ 'neighbors' ] ) {
					if ( ! featureInfo[ neighbor ] ) continue;
					data.addEdgeFor( feature[ 'node' ], featureInfo[ neighbor ][ 'node' ] );
				}
			}
			
			data.edges.setProperty( "lineColor", 0xff0099cc );
			data.edges.setProperty( "lineAlpha", .5 );
			
			// create the visualization of the state data
			var vis : Visualization = new Visualization( data );
			vis.bounds = new Rectangle( 0, 0, BOUNDS.width, BOUNDS.height );
			
			// create a new force directed layout and apply to the visualization
			var layout : ForceDirectedLayout = new ForceDirectedLayout( true );
			vis.operators.add( layout );
			
			// update the vis and add to the display list
			vis.update();
			addChild( vis );
			vis.x = vis.y = 50;
			
// --- uncomment the following and the force-directed layout will actually start taking effect, but you'll need to modify node forces to keep the nodes apart			
			//vis.continuousUpdates = true;
			
			// so you can drag the circles around
			vis.controls.add( new DragControl( NodeSprite ) );
		}
		
		// uses my LambertConformalConic projection class, a projection often used to display the continental U.S.A.
		private function projectionFunction( point : Object ) : Object
		{	
			return LambertConformalConic.projectPoint( point.y, point.x, 1000, -1000, 500, -500, STANDARD_PARALLEL_1, STANDARD_PARALLEL_2, LATITUDE_ORIGIN, LONGITUDE_ORIGIN );
		}
		
	}
}